/*-------------------------------------------------------------------------
 * drawElements Quality Program Random Shader Generator
 * ----------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Variable Type class.
 *//*--------------------------------------------------------------------*/

#include "rsgVariableType.hpp"
#include "rsgToken.hpp"

using std::vector;

namespace rsg
{

VariableType& VariableType::operator= (const VariableType& other)
{
	if (this == &other)
		return *this;

	delete m_elementType;

	m_elementType	= DE_NULL;
	m_baseType		= other.m_baseType;
	m_precision		= other.m_precision;
	m_typeName		= other.m_typeName;
	m_numElements	= other.m_numElements;
	m_members		= other.m_members;
	m_elementType	= DE_NULL;

	if (other.m_elementType)
		m_elementType = new VariableType(*other.m_elementType);

	return *this;
}

VariableType::VariableType (const VariableType& other)
	: m_elementType(DE_NULL)
{
	*this = other;
}

bool VariableType::operator!= (const VariableType& other) const
{
	if (m_baseType != other.m_baseType)
		return true;
	if (m_precision != other.m_precision)
		return true;
	if (m_numElements != other.m_numElements)
		return true;
	if (!!m_elementType != !!other.m_elementType)
		return true;
	if (m_elementType && *m_elementType != *other.m_elementType)
		return true;
	if (m_members != other.m_members)
		return true;
	return false;
}

bool VariableType::operator== (const VariableType& other) const
{
	return !(*this != other);
}

int VariableType::getScalarSize (void) const
{
	switch (m_baseType)
	{
		case TYPE_VOID:
		case TYPE_FLOAT:
		case TYPE_INT:
		case TYPE_BOOL:
		case TYPE_SAMPLER_2D:
		case TYPE_SAMPLER_CUBE:
			return m_numElements;

		case TYPE_STRUCT:
		{
			int sum = 0;
			for (vector<Member>::const_iterator i = m_members.begin(); i != m_members.end(); i++)
				sum += i->getType().getScalarSize();
			return sum;
		}

		case TYPE_ARRAY:
		{
			DE_ASSERT(m_elementType);
			return m_elementType->getScalarSize() * m_numElements;
		}

		default:
			DE_ASSERT(false);
			return 0;
	}
}

int VariableType::getMemberScalarOffset (int memberNdx) const
{
	DE_ASSERT(isStruct());

	int curOffset = 0;
	for (vector<Member>::const_iterator i = m_members.begin(); i != m_members.begin() + memberNdx; i++)
		curOffset += i->getType().getScalarSize();

	return curOffset;
}

int VariableType::getElementScalarOffset (int elementNdx) const
{
	DE_ASSERT(isArray());
	return elementNdx * getElementType().getScalarSize();
}

const VariableType& VariableType::getScalarType (Type baseType)
{
	switch (baseType)
	{
		case TYPE_FLOAT:
		{
			static const VariableType s_floatTypes[] =
			{
				VariableType(TYPE_FLOAT, 1)
				// \todo [pyry] Extend with different precision variants?
			};
			return s_floatTypes[0];
		}

		case TYPE_INT:
		{
			static const VariableType s_intTypes[] =
			{
				VariableType(TYPE_INT, 1)
			};
			return s_intTypes[0];
		}

		case TYPE_BOOL:
		{
			static const VariableType s_boolTypes[] =
			{
				VariableType(TYPE_BOOL, 1)
			};
			return s_boolTypes[0];
		}

		case TYPE_SAMPLER_2D:
		{
			static const VariableType sampler2DType = VariableType(TYPE_SAMPLER_2D, 1);
			return sampler2DType;
		}

		case TYPE_SAMPLER_CUBE:
		{
			static const VariableType samplerCubeType = VariableType(TYPE_SAMPLER_CUBE, 1);
			return samplerCubeType;
		}

		default:
			DE_ASSERT(DE_FALSE);
			throw Exception("VariableType::getScalarType(): unsupported type");
	}
}

const VariableType& VariableType::getElementType (void) const
{
	DE_ASSERT(m_precision == PRECISION_NONE); // \todo [pyry] Precision
	switch (m_baseType)
	{
		case TYPE_FLOAT:
		case TYPE_INT:
		case TYPE_BOOL:
		case TYPE_SAMPLER_2D:
		case TYPE_SAMPLER_CUBE:
			return getScalarType(m_baseType);

		case TYPE_ARRAY:
		{
			DE_ASSERT(m_elementType);
			return *m_elementType;
		}

		default:
			DE_ASSERT(DE_FALSE);
			throw Exception("VariableType::getElementType(): unsupported type");
	}
}

void VariableType::tokenizeShortType (TokenStream& str) const
{
	switch (m_precision)
	{
		case PRECISION_LOW:		str << Token::LOW_PRECISION;	break;
		case PRECISION_MEDIUM:	str << Token::MEDIUM_PRECISION;	break;
		case PRECISION_HIGH:	str << Token::HIGH_PRECISION;	break;
		default:				/* nothing */					break;
	}

	switch (m_baseType)
	{
		case TYPE_VOID:
			str << Token::VOID;
			break;

		case TYPE_FLOAT:
			switch (m_numElements)
			{
				case 1:		str << Token::FLOAT;		break;
				case 2:		str << Token::VEC2;			break;
				case 3:		str << Token::VEC3;			break;
				case 4:		str << Token::VEC4;			break;
				default:	DE_ASSERT(DE_FALSE);		break;
			}
			break;

		case TYPE_INT:
			switch (m_numElements)
			{
				case 1:		str << Token::INT;			break;
				case 2:		str << Token::IVEC2;		break;
				case 3:		str << Token::IVEC3;		break;
				case 4:		str << Token::IVEC4;		break;
				default:	DE_ASSERT(DE_FALSE);		break;
			}
			break;

		case TYPE_BOOL:
			switch (m_numElements)
			{
				case 1:		str << Token::BOOL;			break;
				case 2:		str << Token::BVEC2;		break;
				case 3:		str << Token::BVEC3;		break;
				case 4:		str << Token::BVEC4;		break;
				default:	DE_ASSERT(DE_FALSE);		break;
			}
			break;

		case TYPE_SAMPLER_2D:		str << Token::SAMPLER2D;	break;
		case TYPE_SAMPLER_CUBE:		str << Token::SAMPLERCUBE;	break;

		case TYPE_STRUCT:
			DE_ASSERT(m_typeName != "");
			str << Token(m_typeName.c_str());
			break;

		case TYPE_ARRAY:
			DE_ASSERT(m_elementType);
			m_elementType->tokenizeShortType(str);
			str << Token::LEFT_BRACKET << Token(m_numElements) << Token::RIGHT_BRACKET;
			break;

		default:
			DE_ASSERT(DE_FALSE);
			break;
	}
}

} // rsg
